using UnityEngine;
using System.Collections;

/// <summary>
/// Moving gameobject along a specified path, allowing switch to
/// left or right of the path smoonthly.
/// </summary>
public class PathMotor : MonoBehaviour
{
	/// <summary>
	/// Speed on x-z plane.
	/// </summary>
	public float speed = 20.0f;
	
	/// <summary>
	/// horizontal speed when switch track
	/// </summary>
	public float hSpeed = 5.0f;
	
	/// <summary>
	/// gravity.
	/// </summary>
	public float gravity = 20.0f;
	
	/// <summary>
	/// start moving along a specified path.
	/// </summary>
	public void StartMove (PathQuinticBezier path)
	{
		m_path = path;
		m_state = MoveState.Moving;
		m_coveredLength = 0;
		
		gameObject.transform.position = m_path.GetAnchor (0);
	}
	
	/// <summary>
	/// Switch from right track to middle track, or from middle track to
	/// left track. If a track-switching is happening, nothing will be done.
	/// </summary>
	public void GotoLeftTrack ()
	{
		if (m_trackState == TrackState.Middle)
		{
			m_trackState = TrackState.MiddleToLeft;
			m_trackSwitchTime = Time.time;
		}
		else if (m_trackState == TrackState.Right)
		{
			m_trackState = TrackState.RightToMiddle;
			m_trackSwitchTime = Time.time;
		}
	}
	
	/// <summary>
	/// Switch from left track to middle track, or from middle track to
	/// left track. If a track-switching is happening, nothing will be done.
	/// </summary>
	public void GotoRightTrack ()
	{
		if (m_trackState == TrackState.Left)
		{
			m_trackState = TrackState.LeftToMiddle;
			m_trackSwitchTime = Time.time;
		}
		else if (m_trackState == TrackState.Middle)
		{
			m_trackState = TrackState.MiddleToRight;
			m_trackSwitchTime = Time.time;
		}
	}
	
	/// <summary>
	/// Calculate offset when moving, used when current track is not the middle one
	/// </summary>
	void CalcOffsetSide ()
	{
		m_offsetSideXZ.Set (m_movingTagentXZ.z, 0, -m_movingTagentXZ.x);
		float hSpace = 0;
		
		if (m_trackState == TrackState.Middle)
		{
			m_offsetSideXZ.Set (0, 0, 0);
		}
		else if (m_trackState == TrackState.Left)
		{
			hSpace = - m_trackSpace;
		}
		else if (m_trackState == TrackState.Right)
		{
			hSpace = m_trackSpace;
		}
		else
		{
			hSpace = (Time.time - m_trackSwitchTime) * hSpeed;
			bool stateChanged = (hSpace >= m_trackSpace);
			if (stateChanged)
			{
				hSpace = m_trackSpace;
			}
			
			if (m_trackState == TrackState.LeftToMiddle)
			{
				hSpace -= m_trackSpace;
			}
			else if (m_trackState == TrackState.MiddleToLeft)
			{
				hSpace = -hSpace;
			}
			else if (m_trackState == TrackState.RightToMiddle)
			{
				hSpace = m_trackSpace - hSpace;
			}
			
			if (stateChanged)
			{
				if (m_trackState == TrackState.LeftToMiddle 
					|| m_trackState == TrackState.RightToMiddle)
				{
					m_trackState = TrackState.Middle;
				}
				else if (m_trackState == TrackState.MiddleToLeft)
				{
					m_trackState = TrackState.Left;
				}
				else if (m_trackState == TrackState.MiddleToRight)
				{
					m_trackState = TrackState.Right;
				}
			}
		}
		
		float offsetLen = m_offsetSideXZ.magnitude;
		if (offsetLen != 0)
		{
			float lenRate = hSpace / offsetLen;
			m_offsetSideXZ.Set (m_offsetSideXZ.x * lenRate, 0, m_offsetSideXZ.z * lenRate);
		}
	}
	
	/// <summary>
	/// Current positon on X-Z plane.
	/// </summary>
	public Vector3 positionOnPathXZ
	{
		get { return m_positionOnPathXZ; }
	}
	
	void Awake ()
	{
		m_characterController = gameObject.GetComponent<CharacterController> ();
		m_path = null;
		m_state = MoveState.Idle;
		m_trackState = TrackState.Middle;
	}
	
	void Update ()
	{
		Vector3 movement = Vector3.zero;
		if (m_state == MoveState.Moving)
		{
			float distDeltaThisFrame = speed * Time.deltaTime;
			m_coveredLength += distDeltaThisFrame;
			if (m_coveredLength > m_path.sampledLength)
			{
				m_state = MoveState.Idle;
			}
			
			m_positionOnPathXZ = m_path.GetXZPointOnCurve (m_coveredLength, out m_movingTagentXZ);
			Vector3 currentP = gameObject.transform.position;
			CalcOffsetSide ();
			gameObject.transform.LookAt (currentP + m_movingTagentXZ);
			// move to m_positionOnPathXZ + m_offsetSideXZ with a proper y
			movement = m_positionOnPathXZ + m_offsetSideXZ - currentP; 
			movement.y = 0;
		}
		
		movement.y -= gravity;
		m_characterController.Move (movement);
	}
	
	/// <summary>
	/// We use CharacterController as the object being moved.
	/// </summary>
	CharacterController m_characterController;
	
	/// <summary>
	/// length already moved on current path
	/// </summary>
	float m_coveredLength;
	
	/// <summary>
	/// space between adjacent tracks.
	/// </summary>
	float m_trackSpace = 2.0f; 
	
	/// <summary>
	/// Time when track-switching start.
	/// </summary>
	float m_trackSwitchTime = 0;
	
	/// <summary>
	/// offset used when not moving along middle track.
	/// </summary>
	Vector3 m_offsetSideXZ = Vector3.zero; 
	
	/// <summary>
	/// moving tagent.
	/// </summary>
	Vector3 m_movingTagentXZ = Vector3.zero;
	
	/// <summary>
	/// The m_path position.
	/// </summary>
	Vector3 m_positionOnPathXZ = Vector3.zero;
	
	/// <summary>
	/// current path follwing when moving.
	/// </summary>
	PathQuinticBezier m_path;
	
	/// <summary>
	/// current move state.
	/// </summary>
	MoveState m_state;
	
	/// <summary>
	/// Move state.
	/// </summary>
	enum MoveState
	{
		Idle,
		Moving
	}
	
	/// <summary>
	/// current track state.
	/// </summary>
	TrackState m_trackState;
	
	/// <summary>
	/// Track state.
	/// </summary>
	enum TrackState
	{
		Middle,
		MiddleToLeft,
		Left,
		LeftToMiddle,
		MiddleToRight,
		Right,
		RightToMiddle
	}
}

